/*
 * Copyright (c) 2000
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Written by Tom Henderson
 * Version 1.0: August 2000
 */

#ifdef STP
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/kernel.h>

#include <net/route.h>
#include <net/if.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_var.h>
#include <netinet/in_pcb.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/stp.h>
#include <netinet/stp_var.h>

#ifdef STP_DEBUG

void _Assert(char *strFile, unsigned uLine)
{
	printf("\nAssertion failed: %s, line %u\n", strFile, uLine);
}
#endif
/*
 * Target size of STP PCB hash table.  Will be rounded down to a prime number.
 */
#ifndef SCBHASHSIZE
#define SCBHASHSIZE 128
#endif

/*
 * STP initialization
 */
void
stp_init()
{

	/* random value initializations for stp_instance */
	stp_instance = (u_short) random();

	LIST_INIT(&scb);
	scbinfo.listhead = &scb;
	scbinfo.hashbase = hashinit(SCBHASHSIZE, M_PCB, &scbinfo.hashmask);
	if (max_protohdr < sizeof(struct stpiphdr))
		max_protohdr = sizeof(struct stpiphdr);
	if (max_linkhdr + sizeof(struct stpiphdr) > MHLEN)
		panic("stp_init");
	stp_sendspace = 1024 * 8;
	stp_recvspace = 1024 * 8;

}

/* "new" is number of segments which have been acked and which we can build */
void
stp_opencwnd(sp, new)
	struct stpcb *sp;
	int new;
{
	int i;
	u_long cwnd = sp->snd_cwnd;
	u_long ssthresh = sp->snd_ssthresh;
	u_int incr = sp->s_maxseg;
	
	for (i = 0; i < new; i++) {
		if (cwnd > ssthresh)
			incr = incr * incr / cwnd;
		sp->snd_cwnd = cwnd + incr;
		cwnd = sp->snd_cwnd;
	}
	/* If we've backed off, but now have recovered, turn off backoff */
	if (seq_gt(sp->snd_una, sp->snd_recover))
		sp->snd_backoff = 0;
	cwnd = min(cwnd, STP_MAXWIN * sp->s_maxseg);
}	

/*
 * Set snd_cwnd to one half of its value.  Do not change ssthresh.  Only
 * reduce once per loss event (snd_recover tracks when we leave recovery). 
 */
void
stp_closecwnd(sp)
	struct stpcb *sp;
{
	u_int win;

	if (seq_gt(sp->snd_una, sp->snd_recover)) {
		win = min(sp->snd_wnd * sp->s_maxseg, sp->snd_cwnd) / 2 / 
		    sp->s_maxseg;
		if (win < 1)
			win = 1;
		if (win < INITIAL_CWND)  
			win = INITIAL_CWND;
		sp->snd_cwnd = win * sp->s_maxseg;
 		/* Disable future slow starts (unless cwnd gets cut to 1) */
		sp->snd_ssthresh = 2 * sp->s_maxseg;
		sp->snd_recover = sp->snd_nxt;
	}
}

/*
 * Create template to be used to send stp packets on a connection.
 * Call after host entry created, allocates an mbuf and fills
 * in a skeletal stp/ip header, minimizing the amount of work
 * necessary when the connection is used.
 */
struct stpiphdr *
stp_template(sp)
	struct stpcb *sp;
{
	register struct inpcb *inp = sp->s_inpcb;
	register struct mbuf *m;
	register struct stpiphdr *n;

	if ((n = sp->s_template) == 0) {
		m = m_get(M_DONTWAIT, MT_HEADER);
		if (m == NULL)
			return (0);
		m->m_len = sizeof (struct stpiphdr);
		n = mtod(m, struct stpiphdr *);
	}
	n->si_next = n->si_prev = 0;
	n->si_x1 = 0;
	n->si_pr = IPPROTO_STP;
	n->si_len = htons(sizeof (struct stpiphdr) - sizeof (struct ip));
	n->si_src = inp->inp_laddr;
	n->si_dst = inp->inp_faddr;
	n->si_sport = inp->inp_lport;
	n->si_dport = inp->inp_fport;
	n->si_seq = 0;
	n->si_sum = 0;
	n->si_inc = 0;
	return (n);
}

void stp_initialize_cwnd(sp)
	struct stpcb *sp;
{
	sp->snd_cwnd = INITIAL_CWND * sp->s_maxseg;
	sp->snd_ssthresh = STP_MAXWIN * sp->s_maxseg; 
}

/*
 * Create a new STP control block, making an
 * empty reassembly queue and hooking it to the argument
 * protocol control block.
 */
struct stpcb *
stp_newstpcb(inp)
	struct inpcb *inp;
{
	struct socket *so;
	register struct stpcb *sp;
	int i;

	/* the stpcb will be stored in an mbuf */
	sp = malloc(sizeof(*sp), M_PCB, M_NOWAIT);
	if (sp == NULL)
		return ((struct stpcb *)0);
	bzero((char *) sp, sizeof(struct stpcb));
	sp->seg_next = sp->seg_prev = (struct stpiphdr *)sp;

	/* Need to revisit the code below for setting MSS */
	sp->s_maxseg =  sp->s_peermaxseg = STP_MSS;

	sp->rcv_wnd = sp->snd_wnd = INITIAL_WINDOW;
	sp->s_state = STP_CLOSED;

	sp->s_inpcb = inp;
	/* Obtain an initial estimate of rtt from the BGN/BGNAK exchange. */
	sp->s_srtt = sp->s_rttvar = 0;
	
	/* Initially, no timers running */
	sp->s_timer = sp->keepalive_counter = sp->persist_counter = 
	    sp->s_idle = 0;
	sp->no_stat_response = 0;
	sp->no_progress = 0;
	sp->data_counter = 0;
	so = sp->s_inpcb->inp_socket;
	sp->s_oobflags = 0;

	stp_instance++;
	sp->s_instance = stp_instance;
	/* Initialize sequence numbers (ready to send out # "snd_iss" */
	sp->snd_iss = (((u_long) random()) << 8) >> 8;	
	sp->snd_nxt = sp->snd_una = sp->snd_recover = sp->snd_iss;
	sp->snd_off = sp->snd_up = 0;
	
	stp_initialize_cwnd(sp);
	sp->last_send = current_time();
	sp->snd_backoff = 0;
	sp->stp_nodelay =  sp->disc_pending = sp->shutdown_pending = FALSE;
	sp->tinygram_out = FALSE;
	sp->oob_pending =  sp->end_sent = FALSE;
	sp->ustat_pending = 0;
	sp->last_poll_tstamp = 0;

	sp->poll_pending = sp->send_pending = 0;
	sp->trans_stack = sp->burst_size = 0;

	for (i=0; i< MAXBUFFER_STP; i++) {
		sp->trans_buffer[i].seqno = 0;
		sp->trans_buffer[i].timesent = 0;
		sp->trans_buffer[i].num_bytes = 0;
		sp->trans_buffer[i].num_trans = 0;
	}

	inp->inp_ip_ttl = ip_defttl;
	inp->inp_ppcb = (caddr_t) sp;
	return (sp);
}

/*
 * Drop a STP connection, reporting
 * the specified error.  If connection is synchronized,
 * then send a RST to peer.
 */
struct stpcb *
stp_drop(sp, errno)
	register struct stpcb *sp;
	int errno;
{
	struct socket *so = sp->s_inpcb->inp_socket;

	sp->s_state = STP_CLOSED;

	/* Reset connection if active */
	if (sp->s_state >= STP_EST) {
		sp->s_state = STP_CLOSED;
	    stp_sndrst(sp); 
	} 
	/* If a softerror was received on the connection, this becomes errno */
	if (errno == ETIMEDOUT && sp->s_softerror)
		errno = sp->s_softerror;
	so->so_error = errno;
	DBG(printf("Dropping with error no. %d\n", errno););
	return (stp_close(sp));
}

/*
 *  Close a STP control block (resource release).
 *	- discard all space held by the stp
 *	- discard internet protocol block
 *	- wake up any sleepers
 */
struct stpcb *
stp_close(sp)
    register struct stpcb *sp;
{  
	struct stpiphdr *s;
	struct stpiphdr *siprev;
	struct inpcb *inp = sp->s_inpcb;
	struct socket *so = inp->inp_socket;
	struct mbuf *m;

	/* Kill any timers */
	stp_killtimers(sp);
	/* free the reassembly queue, if any */
	s = sp->seg_next;
	while (s != (struct stpiphdr *) sp) {
		s = (struct stpiphdr *) s->si_next;
		m = REASS_MBUF_STP((struct stpiphdr *) s->si_prev);
		remque(s->si_prev);
		m_freem(m);
	}
	if (sp->s_template)
		(void) m_free(dtom(sp->s_template));
	free(sp, M_PCB);
	inp->inp_ppcb = 0;
	soisdisconnected(so);

	in_pcbdetach(inp);
	return ((struct stpcb *) 0);
}

void 
stp_killtimers(sp)
	register struct stpcb *sp;
{
	if (sp->s_timer) {
		if (sp->s_timer & STP_TIMER_SEND)
			stp_cancel_timer(sp, STP_TIMER_SEND);
		if (sp->s_timer & STP_TIMER_BGN)  
			stp_cancel_timer(sp, STP_TIMER_BGN);
		if (sp->s_timer & STP_TIMER_END)  
			stp_cancel_timer(sp, STP_TIMER_END);
		if (sp->s_timer & STP_TIMER_KEEPALIVE) 
			stp_cancel_timer(sp, STP_TIMER_KEEPALIVE);
		if (sp->s_timer & STP_TIMER_PERSIST) 
			stp_cancel_timer(sp, STP_TIMER_PERSIST);
		sp->s_timer = 0;
	}
}

/* Called by m_reclaim when the kernel runs out of mbufs; does nothing. */
void
stp_drain()
{
}

/*
 * Notify a stp user of a soft error such as host unreachable.
 * Store error as soft error, but wake up user
 * (for now, won't do anything until can select for soft error).
 */
void
stp_notify(inp, error)
	struct inpcb *inp;
	int error;
{
	register struct stpcb *sp = (struct stpcb *)inp->inp_ppcb;
	register struct socket *so = inp->inp_socket;

	/*
	 * Ignore some errors if we are hooked up.
	 * If connection hasn't completed, has retransmitted three times,
	 * and receives a second error, give up now.  This is better
	 * than waiting a long time to establish a connection that
	 * can never complete.
	 */
	if ((sp->s_state == STP_EST || sp->s_state == STP_CLOSE_RECV) &&
	     (error == EHOSTUNREACH || error == ENETUNREACH ||
	      error == EHOSTDOWN)) {
		sp->s_softerror = error;
		return;
	} else if (sp->s_state < STP_EST && sp->cc_count > 3 &&
	    sp->s_softerror)
		/* 
		 * By setting this socket error, the socket becomes readable 
		 * and writeable if process calls select.
		 */
		so->so_error = error;
	else 
		sp->s_softerror = error;
	/* Processes waiting to recv or send are awakened to receive the error*/
	wakeup((caddr_t) &so->so_timeo);
	sorwakeup(so);
	sowwakeup(so);
}

/*
 * This function processes five ICMP errors: destination unreachable, 
 * parameter problem, source quench, time exceeded, and redirect.
 * All redirects passed to STP, TCP, and UDP.  For the other four errors,
 * stp_ctlinput is called only if a STP segment caused the error.
 * A placeholder exists for PRC_MSGSIZE if MTU discovery is implemented. 
 */
void
stp_ctlinput(cmd, sa, vip)
	int cmd;
	struct sockaddr *sa;
	void *vip;
{
	register struct ip *ip = vip;
	register struct stphdr *sh;
	void (*notify) (struct inpcb *, int) = stp_notify;

	if (cmd == PRC_QUENCH)
		/* send it to stp_quench */
		notify = stp_quench;
	else if (cmd == PRC_MSGSIZE)
		/*
		 * Path MTU discovery currently not implemented.  If 
		 * implemented in future, put the function here.
		 */
		; /* Do nothing */ 
	else if (!PRC_IS_REDIRECT(cmd) &&
		 ((unsigned)cmd > PRC_NCMDS || inetctlerrmap[cmd] == 0))
		return;
	if (ip) {
		sh = (struct stphdr *)((caddr_t)ip + (ip->ip_hl << 2));
		in_pcbnotify(&scb, sa, sh->sh_dport, ip->ip_src, sh->sh_sport, 
			cmd, notify);
	} else
		(void) in_pcbnotify(&scb, sa, 0, zeroin_addr, 0, cmd, notify);
}

/*
 * When a source quench is received, close congestion window
 * to one segment if CLOSE_ON_QUENCH is TRUE.  
 */
void
stp_quench(inp, errno)
	struct inpcb *inp;
	int errno;
{
	struct stpcb *sp = intostpcb(inp);

	if (sp && CLOSE_ON_QUENCH)
		sp->snd_cwnd = sp->s_maxseg;
}

/* Returns in ms the time elapsed since start ms (assumes no wraparound) */
int 
time_elapsed(start)
	u_long start;
{
	struct timeval t;

	microtime(&t);
	return ((int) ((t.tv_sec)*1000 + (t.tv_usec)/1000 - start));
}  

u_long 
current_time()
{
	struct timeval t;
	microtime(&t);
	return ((t.tv_sec)*1000 + (t.tv_usec)/1000);
}

void
stp_updatertt(sp, rtt)
	struct stpcb *sp;
	int rtt;
{
	int delta;

	if (sp->s_srtt == 0) {	/* First measurement, set rttvar to rtt/2 */
		sp->s_srtt = (rtt << SRTT_SHIFT);
		sp->s_rttvar = sp->s_srtt/2;
	} else {
		/* 
		 * s_srtt is stored as fixed point with 3 bits after the binary
		 * point (i.e., scaled by 8).  This code implements the 
		 * following: * s_srtt = rtt/8 + s_srtt * (7/8)
		 */
		delta = rtt - (sp->s_srtt >> SRTT_SHIFT);
		/* 7 is (1 << SRTT_SHIFT) - 1 */
		sp->s_srtt = sp->s_srtt + delta;
		/*
		 * s_rttvar is a smoothed mean difference stored as fixed point
		 * with 3 bits after the binary point (i.e., scaled by 8).
		 * s_rttvar = s_rttvar * (7/8) + |delta| / 8
		 */
		if (delta < 0)
			delta = -delta;
		delta -= (sp->s_rttvar >> SRTT_SHIFT);
		sp->s_rttvar += delta;
	}
	/* smoothed rtt and variance are at least 1 ms */
	if ((sp->s_srtt >> SRTT_SHIFT) < 1)
		sp->s_srtt = (1 << SRTT_SHIFT);
	if ((sp->s_rttvar >> SRTT_SHIFT) < 1)
		sp->s_rttvar = (1 << SRTT_SHIFT);
}

void
stp_start_timer(sp, type, interval)
	struct stpcb *sp;
	int type, interval;
{
	switch (type) {
	case STP_TIMER_SEND:
		sp->s_timer |= STP_TIMER_SEND;
		timeout(stp_send_dummy, (void *) sp, interval);
		break;
	case STP_TIMER_BGN:
		sp->s_timer |= STP_TIMER_BGN;
		timeout(stp_sendcc_dummy, (void *) sp, interval);
		break;
	case STP_TIMER_END:
		sp->s_timer |= STP_TIMER_END;
		timeout(stp_sendcc_dummy, (void *) sp, interval);
		break;
	case STP_TIMER_PERSIST:
		sp->persist_counter = STP_PERSIST;
		sp->s_timer |= STP_TIMER_PERSIST;
		break;
	case STP_TIMER_KEEPALIVE:
		sp->s_timer |= STP_TIMER_KEEPALIVE;
		sp->keepalive_counter = STP_KEEPALIVE;
		break;
	default:
		DBG(printf("Error: stp_start_timer %d\n", type);); 
		break;
	}
}

void
stp_cancel_timer(sp, type)
	struct stpcb *sp;
	int type;
{
	switch (type) {
	case STP_TIMER_SEND:
		if (sp->s_timer & STP_TIMER_SEND) {
			untimeout(stp_send_dummy, (void *) sp);
			sp->s_timer &= ~STP_TIMER_SEND;
		}
		break;
	case STP_TIMER_BGN:
		if (sp->s_timer & STP_TIMER_BGN) {
			untimeout(stp_sendcc_dummy, (void *) sp);
			sp->s_timer &= ~STP_TIMER_BGN;
		} 
		break;
	case STP_TIMER_END:
		if (sp->s_timer & STP_TIMER_END) {
			untimeout(stp_sendcc_dummy, (void *) sp);
			sp->s_timer &= ~STP_TIMER_END;
		}
		break;
	case STP_TIMER_PERSIST:
		sp->s_timer &= ~STP_TIMER_PERSIST;
		sp->persist_counter = 0;
		break;
	case STP_TIMER_KEEPALIVE:
		sp->keepalive_counter = 0;
		break;
	default:
		DBG(printf("Error: stp_cancel_timer %d\n", type);); 
		break;
	}
}
/* Various helper functions */
long 
seq_gt(a,b)
	u_long a, b;
{
	return((long) (a - b) > 0);
}
	
long 
seq_geq(a,b)
	u_long a, b;
{
	return((long) (a - b)  >= 0);
}
	
long
seq_lt(a,b)
	u_long a, b;
{
	return((long) (a - b)  < 0);
}
	
long 
seq_leq(a,b)
	u_long a, b;
{
	return((long) (a - b) <= 0);
}
	
/* assumes a >= b */
u_long 
seq_subtract(a,b)
	u_long a, b;
{
	return ((a - b) % MODULO);
}

u_long 
seq_add(a,b)
	u_long a, b;
{
	return ((a + b) % MODULO);
}
/*
 * STP slow timeout routine.  
 */
void
stp_slowtimo()
{
	struct inpcb *ip, *ipnxt;
	struct stpcb *sp;
	int s = splnet();
	int i;

	/* 
  	 * Search through scb's and update active timers
	 */
	ip = scb.lh_first;
	if (ip == 0) {
		splx(s);
		return;
	}
	for (; ip != 0; ip = ipnxt) {
		ipnxt = ip->inp_list.le_next;
		sp = intostpcb(ip);
		if (sp == 0)
			continue;
		if ((sp->s_timer & STP_TIMER_PERSIST) && 
		    --sp->persist_counter == 0) {
			/* persist timer has expired; snd_poll will reset it */
			stp_sndpoll(sp);
		} else if ((sp->s_timer & STP_TIMER_KEEPALIVE) && 
		    --sp->keepalive_counter == 0) {
			/* keepalive timer has expired */
			if (sp->s_state < STP_EST) {
				DBG(printf("KEEPALIVE timer expired, port %d\n", sp->s_inpcb->inp_lport););
				sp = stp_drop(sp, ETIMEDOUT);
				continue;
			} else if (sp->s_inpcb->inp_socket->so_options & 
			    SO_KEEPALIVE) {  
				if (sp->s_idle < STP_KEEPALIVE) {
					/* 
					 * got a response from peer recently, 
					 * so reset this timer
					 */
					stp_start_timer(sp, 
					    STP_TIMER_KEEPALIVE, NULL);
				} else if (sp->s_idle >= STP_KEEPALIVE + 
				    (STP_KEEPALIVE_MAX * STP_KEEPALIVE_INT)) {
					/* 
					 * No response from peer after 
					 * KEEPALIVE_MAX tries 
					 */
					sp = stp_drop(sp, ETIMEDOUT);
					continue;
				} else {
					/* 
				   	 * POLL to see if connection is still 
					 * up.  Keep STP_TIMER_KEEPALIVE active 
					 */
					stp_sndpoll(sp);
					sp->keepalive_counter = 
					    STP_KEEPALIVE_INT;
				}
			} else {
				/* 
				 * KEEPALIVE socket option not set, so just 
				 * reset timer 
				 */
				stp_start_timer(sp, STP_TIMER_KEEPALIVE, NULL);
			}
		}
		sp->s_idle++;
	}
	splx(s);
	return;
}

/* 
 * STP checksum function.  Similar to Net/3 code in Stevens, vol.2,   
 * for portable in_cksum, but here we do not checksum the IP header. 
 */
#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x)
#define REDUCE {l_util.l = sum; sum = l_util.s[0]+l_util.s[1]; ADDCARRY(sum);}

int
stp_cksum(m, len)
struct mbuf *m;
int len;
{
	u_short *w;
	int sum = 0;
	int mlen = 0;
	int byte_swapped = 0;
	flag fFirst_mbuf = TRUE;

	union {
		char c[2];
		u_short s;
	} s_util;

	union {
		u_short s[2];
		long l;
	} l_util;

	for (; m && len; m= m->m_next) {
		if (m->m_len == 0) 
			continue;
		if (fFirst_mbuf) 
			/* 
			 * Advance data pointer 20 bytes beyond start of 
			 * pseudo-IP pkt.
			 */
			w = mtod(m, u_short *) + 
			    (sizeof (struct ip)/ sizeof (u_short));
		else 
			w = mtod(m, u_short *);
		if (mlen == -1) {
			/*
	  		 * The first byte of this mbuf is the continuation of a
			 * word spanning between this mbuf and the last mbuf.
			 * 
			 * s_util.c[0] is already saved when scanning previous 
			 * mbuf.	
			 */
			s_util.c[1] = *(char *) w;
			sum += s_util.s;
			w = (u_short *) ((char *) w + 1);
			mlen = m->m_len - 1;
			len--;
		} else 
			mlen = m->m_len;
		if (fFirst_mbuf) {
			mlen -= sizeof (struct ip);
			fFirst_mbuf = FALSE;
		}
		if (len < mlen)
			mlen = len;
		len -= mlen;
		/*
		 * Force to even boundary.
		 */
		if ((1 & (int) w) && (mlen > 0)) {
			REDUCE;
			sum <<= 8;
			s_util.c[0] = *(u_char *) w;
			w = (u_short *) ((char *) w + 1);
			mlen --;
			byte_swapped = 1;
		}
		/*
		 * Unroll the loop to make overhead from
		 * branches &c small.
		 */
		while ((mlen -= 32) >= 0) {
			sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
			sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
			sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
			sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
			w += 16;
		}
		mlen += 32;
		while ((mlen -= 8) >= 0) {
			sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
			w += 4;
		}
		mlen += 8;
		if (mlen == 0 && byte_swapped == 0)
			continue;
		REDUCE;
		while ((mlen -= 2) >= 0) {
			sum += *w++;
		}
		if (byte_swapped) {
			REDUCE;
			sum <<= 8;
			byte_swapped = 0;
			if (mlen == -1) {
				s_util.c[1] = *(char *) w;
				sum += s_util.s;
				mlen = 0;
			} else
				mlen = -1;
		} else if (mlen == -1)
			s_util.c[0] = *(char *) w;
	}
	if (len)
		DBG(printf("cksum: out of data\n"););
	if (mlen == -1) {
		/* 
		 * The last mbuf has odd # of bytes.  Follow the standard 
		 * (the odd byte may be shifted left by 8 bits or not as 
		 * determined by endian-ness of the machine).
		 */
		s_util.c[1] = 0;
		sum += s_util.s;
	}
	REDUCE;
	return (~sum & 0xffff);
}

#endif
